﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;

namespace ReactiveCode.Statements
{
    class Block : ReactiveStatement, IStatementParent
    {
        class StatementCollection : Collection<ReactiveStatement>
        {
            Block _owner;
            public StatementCollection(Block owner)
            {
                _owner = owner;
            }

            void OnInserting(ReactiveStatement stmt)
            {
                Contract.Requires(stmt != null && stmt.Parent == null);
            }
            void OnRemoved(ReactiveStatement stmt)
            {
                stmt.Parent = null;
                stmt.Enabled = false;
            }

            protected override void InsertItem(int index, ReactiveStatement stmt)
            {
                OnInserting(stmt);
                base.InsertItem(index, stmt);
                stmt.Parent = _owner;

                if (index <= _owner.DisableAfter)
                {
                    _owner._disableAfter++;
                    stmt.Enabled = true;
                }
                else
                {
                    stmt.Enabled = false;
                }
            }
            protected override void SetItem(int index, ReactiveStatement stmt)
            {
                OnInserting(stmt);

                var old = this[index];
                bool wasEnabled = old.Enabled;
                base.SetItem(index, stmt);
                OnRemoved(old);

                stmt.Parent = _owner;
                stmt.Enabled = wasEnabled;
            }
            protected override void RemoveItem(int index)
            {
                var stmt = this[index];
                base.RemoveItem(index);
                OnRemoved(stmt);

                if (index < _owner.DisableAfter)
                {
                    _owner._disableAfter--;
                }
                else if (index == _owner.DisableAfter)
                {
                    Contract.Assert(_owner.Disablers.First() == stmt);
                    _owner.UpdateDisableAfter();
                }
            }
            protected override void ClearItems()
            {
                var stmts = this.ToList();
                base.ClearItems();
                foreach (var stmt in stmts)
                    OnRemoved(stmt);
                _owner.UpdateDisableAfter();
            }
        }

        public ObservableCollection<ReactiveStatement> Disablers { get; private set; }

        int _disableAfter;
        int DisableAfter
        {
            get { return _disableAfter; }
            set
            {
                if (_disableAfter == value)
                    return;
                _disableAfter = value;
                for (int i = 0; i < Body.Count; i++)
                    Body[i].EnabledProperty.Value = i <= value;
            }
        }
        void UpdateDisableAfter()
        {
            if (!Enabled)
                DisableAfter = -1;
            else 
                DisableAfter = Disablers.Select(Body.IndexOf).Where(i => i >= 0)
                    .Min(i => (int?)i) ?? int.MaxValue;
        }

        public Collection<ReactiveStatement> Body { get; private set; }
        IList<ReactiveStatement> IStatementParent.Children
        {
            get { return Body; }
        }

        public Block()
        {
            Body = new StatementCollection(this);
            Disablers = new ObservableCollection<ReactiveStatement>();
            EnabledProperty.Subscribe(_ => UpdateDisableAfter());
            Disablers.CollectionChanged += (s, e) => UpdateDisableAfter();
        }
        public override void Dispose()
        {
            base.Dispose();
            foreach (var stmt in Body)
                stmt.Dispose();
        }

        public override string ToString()
        {
            if (!string.IsNullOrWhiteSpace(Name))
                return Name;
            return string.Join(Environment.NewLine, Body);
        }
    }
}
